<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Vue-自定义组件</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>

<body>
    <!-- 注册一个全局的vue 组件 -->
    <div id="app-1">
        <my-component></my-component>
    </div>

    <div id="app-2">
        <my-component-1></my-component-1>
        <my-component-2></my-component-2>
    </div>

    <!--
        由于DOM模型的限制，不能把一些自定义的组件作为子节点插入到某些html标签内。
        比如<ul> <ol> <table> <select> 限制了它被包裹的元素
        <option> 这一类只出现在特定的元素内部
        
        比如:
        <table>
            <my-row></my-row>
        </table>

        这里的my-row是不能被识别的，会出现渲染错误
        变通的解决办法是使用 is 这个特殊属性

        <table>
            <tr is="my-row"></tr>
        </table>

        这样my-row 这个自定义的组件就绑定到tr上面去了

        另外就是你使用这三种字符串模板，也是没有限制的:
        <script type="text/x-template">
        JavaScript内联模版字符串
        .vue 组件         

    -->

    <!--
        定义自定义的组件的时候，data这个属性必须是一个函数。具体这样设计的考虑就是如果是
        一个对象的话，所有的组件实例就变成了共享同一个data对象了，这样就破坏了组件的独立性

    -->

    <div id="app-3">
        <counter-com></counter-com>
        <counter-com></counter-com>
        <counter-com></counter-com>

        <counter-com2></counter-com2>
        <counter-com2></counter-com2>
        <counter-com2></counter-com2>
    </div>

    <!--
        组件的话意味着协作。组件间可以分成父与子的关系。为了组件的通信和解耦需要，vue内父子
        组件的通信可以用一句话概括:

        props down , events up

        其中props 是子组件定义的属性，父节点通过props向子组件传递数据。
        events则是子组件内部触发的事件。子组件的事件通过events传递到父节点，父节点对感兴趣
        的events进行处理



    -->

    <!-- 自定义组件传参 -->
    <div id="app-4">
        <my-com-props msg="Hello" your-name="Eason"></my-com-props>
    </div>

    <!-- 将父节点的数据与子节点的入参绑定 -->
    <div id="app-5">
        <input v-model="parentMsg">
        <br>
        <my-com-props v-bind:msg="parentMsg" your-name="Eason"></my-com-props>
        <my-com-props :msg="parentMsg" your-name="Mao"></my-com-props>
    </div>

    <!-- 常见的问题，如果想传一个数字给子组件，然后很多人第一反应是这样做:
         <comp some-prop="1"></comp>

         但是实际上子组件获取到的是一个字符类型的"1",而不是一个number类型的1
         要传一个number类型的1,需要用v-bind方法：

         <comp v-bind:some-prop="1"></comp>

     -->

    <!--
         单向数据流。数据从父节点传入子节点，但是子节点不应该将传入的数据进行修改。
         Why?因为传入子节点的有可能是一个对象的引用，也就意味着某一个子节点将其内容
         更改，会影响到父节点的对象，也就是牵一发而动全身，影响了其他的子节点的数据。

         这会大大增加程序的追踪难度，导致状态管理复杂，增加开发成本，所以是要极力避免这种
         做法。

         我们有两种原因想修改prop的数据，分别是:
         1.想当成局部数据使用
         2.要将这个输入的数据进行处理，作为输出

         具体解决办法是：
         1.定义一个局部变量，将值复制过来(对象类型看能不能clone对象)

         props: ['initialCounter'],
         data: function () {
            return { counter: this.initialCounter }
         }

         2.定义计算属性，对输入数据进行处理
         props: ['size'],
         computed: {
            normalizedSize: function () {
                return this.size.trim().toLowerCase()
            }
         }

     -->

     <!--
         子组件需要通过事件来把数据回传给父节点。

         1. 使用$on(eventName) 监听事件
         2. 使用$emit(eventName) 触发事件

     -->

     <div id="counter-event-demo">
         <p>点击总数:{{total}}</p>
         <!--
             父节点通过v-on 监听子节点的事件，这个例子中，increment是子节点自定义的事件
         -->
         <button-counter v-on:increment="incrementTotal"></button-counter>
         <button-counter v-on:increment="incrementTotal"></button-counter>
         <!-- 子组件绑定原生事件 -->
         <button-counter v-on:click.native="doAlert"></button-counter>
     </div>



</body>
<script>
    // 在实例化组件之前，得先注册组件,否则会有显示的问题

    Vue.component('my-component', {
        template: '<div>一个自定义组件!</div>'
    })

    new Vue({
        el: '#app-1'
    })

    // 也可以局部注册，不过这样的组件只能在某个实例中使用
    // 局部注册的组件，这个顺序应该是按照按照注入的顺序

    var Child1 = {
        template: '<p>我爱你，我的家1</p>'
    }
    var Child2 = {
        template: '<p>我爱你，我的家2</p>'
    }

    new Vue({
        el: '#app-2',
        components: {
            'my-component-1': Child1,
            'my-component-2': Child2,
        }
    })

    // 以下代码会编译报错

    //   Vue.component('com',{
    //     template:'<span>{{ msg}}</span>',
    //     data:{
    //         msg:'Hello'
    //     }
    //   })

    // 控制台报错:vue.js:440 [Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.

    //通过模拟方法绕过这个限制，看实际的效果

    var countData = { counter: 0 }

    Vue.component('counter-com', {
        template: '<button v-on:click="counter +=1">{{counter}}</button>',
        data: function () {
            // 通过function定义绕过vue的校验，实际上引用的是同一个data对象
            return countData;
        }
    })
    // 正确的做法是返回一个匿名对象
    Vue.component('counter-com2', {
        template: '<button v-on:click="counter +=1">{{counter}}</button>',
        data: function () {
            return {
                counter: 0
            }
        }
    })

    var app3 = new Vue({
        el: '#app-3'
    })

    // 定义一个有传入属性的组件
    // 由于不是使用字符串模板，这种驼峰命名的属性在dom里面调用的时候，需要转换为kebab-case
    //（短横线隔开式）

    Vue.component('my-com-props', {
        props: ['msg', 'yourName'], // 定义组件
        template: '<span>{{ msg }}-{{yourName}}</span>'
    })

    var app4 = new Vue({
        el: '#app-4',
        data: {

        }
    })

    var app5 = new Vue({
        el: '#app-5',
        data: {
            parentMsg: '你好'
        }
    })

  /*
    在传入子组件的参数中，为了保证传入的参数符合子组件的要求，你可以在子组件内设置
    参数的类型和要求。

    Vue.component('example', {
        props: {
            // 基础类型检测 （`null` 意思是任何类型都可以）
            propA: Number,
            // 多种类型
            propB: [String, Number],
            // 必传且是字符串
            propC: {
            type: String,
            required: true
            },
            // 数字，有默认值
            propD: {
            type: Number,
            default: 100
            },
            // 数组／对象的默认值应当由一个工厂函数返回
            propE: {
            type: Object,
            default: function () {
                return { message: 'hello' }
            }
            },
            // 自定义验证函数
            propF: {
            validator: function (value) {
                return value > 10
            }
            }
        }
        })

        基础类型有如下几种:
        String
        Number
        Boolean
        Function
        Object
        Array
  
  */

  Vue.component('button-counter',{
      template:'<button v-on:click="increment">{{counter}}</button>',
      data:function(){
          return {
              counter:0
          }
      },
      methods: {
          increment:function(){
              this.counter += 1
              //手动触发事件
              this.$emit('increment')
          }
      }
  })

  new Vue({
      el:'#counter-event-demo',
      data:{
          total:0
      },
      methods:{
        incrementTotal:function(){
            this.total +=1
        },
        doAlert:function(e){
            console.log(e)
            alert(this.total)
        }
      }
  })

</script>

</html>